§ Fox3.0 插件开发指南(IOS)
§ 介绍
移动平台使用osgi机制管理插件,以module为功能单位,每个moduel可包含三类文件,分别为boot插件、plugin插件和配置文件,其中boot插件是在平台启动和关闭是调用,如版本更新,缓存清理等,而plugin类型插件用于给javascript提供native能力,如拍照,文件上传下载等,而配置文件则是插件的注册信息。如下图

§ 工程结构说明
§ 目录结构
ios/fox为主工程
Ios/sdk 保留目录,目前暂时不放东西
ios/sdk-core 基础framework库的目录,如logger、通讯、im等
Ios/sdk-three 第三方库的目录,如支付、ocr、二代证等
原则上尽量一个插件代表一个功能,这样有利于后面我们根据需求进行裁剪。
§ 版本文件目录
fox/version 工程资源目录
--------/bundle identifier/configuration/client.properties 配置文件
--------/bundle identifier/tab_images foot tab图标
--------/bundle identifier/workspace web工程目录
PS:bundle identifier中点替换为下划线,例如 fox.app -> fox_app
§ 基础插件目录
fox/app/plugins 为app内置的基础插件目录
内含插件列表:
- bus 共享存储
- comm 通信
- device 外设代理
- file 文件访问
- lifecycle APP生命周期
- logger 日志
- natives native接口
- navigate 导航
- session session插件
- splash splash插件
- system 系统native集合
- version 版本控制
- whitelist 白名单
§ 扩展插件目录
fox/plugins 为app的扩展插件目录
PS:我们开发的插件,绝大多数应该在该目录下,如IM、定位等
§ swift oc桥接文件
(如果OC的接口需要在swift中引用,则需要在上面#import,OC内部的调用则不需要)
fox/app-Bridging-Header.h
§ Boot插件开发
主要是用于APP启动过程中调用,我们一般用于做一些初始化工作,如过期文件清理,版本更新等。
§ swift 代码
//
// VersionReady.swift
// app
//
// Created by 江成 on 2019/8/9.
//
import Foundation
import fox_ninetales
import core
//版本准备
class VersionReady: FXBoot{
/**
* 启动
* activity onCreate方法中调用,在所有boot模块done后启动web view
* 任务完成后必须调用done或cancel方法
*/
override open func start(interface:FXInterface, context:FXProgressContext){
//分发任务
let subContexts = context.distribute(ratios: [20,80])
//版本发布
VersionRelease.instance.release(progressContext: subContexts[0])
//版本更新
VersionUpdater.instance.update(context: subContexts[1])
}
/**
* 关闭
* activity onDestroy方法中调用,在FXPlugin的onDestroy方法后面
*/
override open func stop(interface:FXInterface?){
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
§ 配置文件
配置文件需要手动在模块所在的目录下,新建xml配置文件,文件名的规则fox_extesion_xxx,例如我在模块core_ext_demo中的配置文件的名为fox_extension_core_ext_demo.xml。
<?xml version="1.0" encoding="UTF-8"?>
<plugins>
<!--启动扩展-->
<extension point="fox.extension.boot">
<!--版本准备-->
<boot name="version" class="VersionReady" text="版本部署">
</boot>
</extension>
</plugins>
2
3
4
5
6
7
8
9
§ Plugin插件
根据功能和范围插件主要分为APlugin,Device和Native几类,其中Device插件用于调用系统或第三方提供的外设模块,如相机、OCR、二代证等,而Native则用于集成除外设类之外的原生功能,如定位、网络等,而AProxy则用于功能更为复杂的原生功能调用。我们在原生开发的过程中选择优先顺序是Native&Device > AProxy,也是我们根据功能分类优先选择Native或Device,当两个不合适的情况下才选择AProy。下图是他们之间的关系

§ Device开发指南
Device插件用于集成外设调用,下面是例子
§ swift
//
// DemoOCRDevice.swift
// app
//
// Created by 江成 on 2020/1/2.
//
import Foundation
import core
//Demo OCR Deivce
class DemoOCRDevice: Device{
//外设调用
public override func call(type:String, action:String, param:String, cite:Cite, callback:@escaping(_ code:Int, _ data:Any?)->Void){
//根据js传递过来的动作执行
if action == "test"{
callback(CallbackResult.SUCCESS, "ok")
}else{
callback(CallbackResult.ERROR, "ok")
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
参数说明
type:设备类型,以配置文件中的type对应
action:设备操作动作
callback:回调函数,通过(_ code:Int, _ data:Any?)函数通知web设备调用情况
cite:上下文引用,其方法如下
//应用名称
public var appName:String//应用类型
public var appType:FXAppType//获取文件访问器
public func getFileAccessor()->FileAccessor//获取HTTP访问器
public func getHttpRequester()->HttpRequester
§ 配置
<?xml version="1.0" encoding="UTF-8"?>
<plugins>
<!--外设模块扩展-->
<extension point="fox.extension.device">
<!--OCR测试-->
<device class="DemoOCRDevice" scope="singleton"
type="ocr" typeName="OCR" devId="ocr-demo" devName="测试OCR外设"></device>
</extension>
</plugins>
2
3
4
5
6
7
8
9
配置说明
- type:设备类型,js就是根据type来调用不同的外设的
- typeName:设备类型的描述
- devId:设备的ID必须唯一
- devName:设备的描述
§ js调用
//外设测试
deviceTest:function(){
//type:配置文件上的type保持一致, action, params, callback
fox.device.call("ocr","test","外设测试",(code, message ,data)=>{
//code 0:成功,1:取消,2:失败
//调用成功后,返回数据在data中,如果失败了则message是失败消息
if(code == 0){
fox.layer.open(data);
}else{
fox.layer.open("调用失败,"+message);
}
});
}
2
3
4
5
6
7
8
9
10
11
12
13
参数说明 fox.device.call(type,action,params,callback)是外设调用的js接口,其参数为
- type:外设类型和配置文件上的保持一致
- action:动作,和与原生实现进行约定
- params:参数
- callack:回调函数
§ Native开发指南
Native插件用于集成简单的原生功能,下面是例子
§ swift
//
// DemoNetworkNative.swift
// app
//
// Created by 江成 on 2020/1/2.
//
import Foundation
import core
//Demo Network Native
class DemoNetworkNative: Native{
//native调用
public override func call(action:String, param:String, cite:Cite, callback:@escaping(_ code:Int, _ data:Any?)->Void){
//根据js传递过来的动作执行
if action == "wifiStatus"{
callback(CallbackResult.SUCCESS, "ok")
}else{
callback(CallbackResult.ERROR, "ok")
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
参数说明
action:操作动作
param:参数
callbackContext:回调上下文,通过callback(int code,Object data)函数通知web设备调用情况
cite:上下文引用,其方法如下
//应用名称
public var appName:String//应用类型
public var appType:FXAppType//获取文件访问器
public func getFileAccessor()->FileAccessor//获取HTTP访问器
public func getHttpRequester()->HttpRequester
§ 配置
<?xml version="1.0" encoding="UTF-8"?>
<plugins>
<!--native接口模块扩展-->
<extension point="fox.extension.native">
<native action="wifiStatus" class="DemoNetworkNative">
</native>
</extension>
</plugins>
2
3
4
5
6
7
8
配置说明 fox.native.call(action,params,callback)是native调用的js接口,其参数为
- action:动作,和与原生实现进行约定和配置文件保持一致
- params:参数
- callack:回调函数
§ js调用
//native测试
nativeTest:function(){
// action:配置文件上的action保持一致, params, callback
fox.native.call("wifiStatus","native测试",(code, message ,data)=>{
//code 0:成功,1:取消,2:失败
//调用成功后,返回数据在data中,如果失败了则message是失败消息
if(code == 0){
fox.layer.open(data);
else{
fox.layer.open("调用失败,"+message);
}
});
}
2
3
4
5
6
7
8
9
10
11
12
13
§ APlugin开发指南
一个插件的开发主要包括swift/OC、配置和js三部分
§ swift
插件中暴露给JS调用的方法
//
// DemoPlugin.swift
// app
//
// Created by 江成 on 2020/1/2.
//
import Foundation
import fox_ninetales
import core
import XCGLogger
class DemoPlugin: APlugin{
//获取logger
private lazy var logger = {
return XCGLogger()
}()
//初始化
public required init(){
super.init()
self.logger.info("插件初始化,仅会执行一次")
}
//同步执行
public override func syncExecute(action:String, args:[Any])->Any?{
//TODO:同步执行实现
let message = args[0] as? String ?? "unknown"
logger.info("同步执行,\(message)")
return "fox_device_demo_back:\(message)"
}
//异步步执行
public override func asyncExecute(action:String, args:[Any], callbackContext:FXCallbackContext){
//TODO:异步执行实现
let message = args[0] as? String ?? "unknown"
logger.info("异步执行,\(message)")
callbackContext.success(args: "fox_device_demo_back:\(message)")
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
§ APlugin便利方法
APlugin提供获取http请求器和文件访问器的方法
//获取文件访问器
public func getFileAccessor(appName:String)->FileAccessor
//获取HTTP请求器
public func getHttpRequester(appType:FXAppType, appName:String)->HttpRequester
§ 配置
<?xml version="1.0" encoding="UTF-8"?>
<plugins>
<!--插件扩展-->
<extension point="fox.extension.plugin">
<!--测试插件-->
<plugin name="demoPlugin" class="DemoPlugin" text="测试插件">
</plugin>
</extension>
</plugins>
2
3
4
5
6
7
8
9
10
§ js插件
/**
* Created by 江成
*/
(function (fox, window, factory) {
// 判断是否支持模块定义
let hasDefine = (typeof define === 'function');
if (hasDefine) {
//获取对象
let exports = factory(fox,window);
//定义模块
define(exports);
//安装插件(兼容非模块的访问方式)
window.fox.demo = exports;
} else {
//获取对象
let exports = factory(fox,window);
//安装插件
window.fox.demo = exports;
}
}(fox, window, function (fox,window) {
//定义Demo对象
let demo={
/**
* 同步测试
* @param message
*/
syncFn:function(message) {
//参数
let args = {
service: "demoPlugin", //对应FXProxy注册在配置文件上的服务名
action: "syncFn", //对应FXProxy中标志了@JavascriptInterface的方法
data: [message], //对应方法中的参数
async: false //是否异步
};
return window.fxBridge.exec(args);
},
/**
* 异步测试
* @param message
* @param callback
*/
asyncFn:function(message,callback) {
//参数
let args = {
service: "demoPlugin",//对应FXProxy注册在配置文件上的服务名
action: "asyncFn",//对应FXProxy中标志了@JavascriptInterface的方法
data: [message], //对应方法中的参数
async: true, //是否异步
callback:callback //异步回调函数
};
return window.fxBridge.exec(args);
}
};
return demo;
}));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
§ js调用
//同步测试
demoSyncTest:function(){
let res = fox.demo.syncFn("同步测试");
fox.layer.open(res);
},
//异步测试
demoASyncTest:function(){
fox.demo.asyncFn("异步测试",(status,data)=>{
//status 0:成功,1:取消,2:失败
if(status == '0'){
fox.layer.open(data);
}else{
fox.layer.open("调用失败");
}
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
§ APlugin插件APP生命周期Hook说明
在APlugin中能监听到APP的整个生命周期,我们在对应的回调中,做某些SDK的初始化或销毁工作
//MARK:App Delegate
//APP启动
open func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
//TODO:APP启动处理
}
//APP暂停
open func applicationWillResignActive(_ application: UIApplication) {
//TODO:APP暂停处理
}
//APP进入后台
open func applicationDidEnterBackground(_ application: UIApplication) {
//TODO:APP进入后台处理
}
//APP进入前台
open func applicationWillEnterForeground(_ application: UIApplication) {
//TODO:APP进入前台处理
}
//APP重新激活
open func applicationDidBecomeActive(_ application: UIApplication) {
//TODO:APP重新激活处理
}
//APP终止
open func applicationWillTerminate(_ application: UIApplication) {
//TODO:APP终止处理
}
//APP openURL事件处理
open func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool? {
//TODO:/APP openURL事件处理
return nil
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
§ APlugin插件webview生命周期Hook说明
//MARK:WKNavigationDelegate
//页面开始加载时调用(返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
open func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) -> Bool{
//TODO:页面开始加载时调用处理
return false
}
//当内容开始返回时调用(返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
open func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) -> Bool{
//TODO:当内容开始返回时调用处理
return false
}
// 页面加载完成之后调用(返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
open func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) -> Bool{
//TODO:页面加载完成之后调用
return false
}
//页面加载失败时调用(返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
open func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error)->Bool{
//TODO:页面加载失败时调用
return false
}
// 接收到服务器跳转请求之后调用(返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
open func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!)->Bool{
//TODO:接收到服务器跳转请求之后调用
return false
}
// 在收到响应后,决定是否跳转(返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
open func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) -> Bool{
//TODO:在收到响应后,决定是否跳转
return false
}
// 在发送请求之前,决定是否跳转(返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
open func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) -> Bool{
//TODO:在发送请求之前,决定是否跳转
return false
}
//MARK:WKUIDelegate
//处理网页js中的提示框,若不使用该方法,则提示框无效 (返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
open func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) -> Bool{
//TODO:处理网页js中的提示框,若不使用该方法,则提示框无效
return false
}
//处理网页js中的确认框,若不使用该方法,则确认框无效 (返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
open func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void)-> Bool{
//TODO:处理网页js中的确认框,若不使用该方法,则确认框无效
return false
}
//处理网页js中的文本输入 (返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
open func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void)->Bool{
//TODO:处理网页js中的文本输入
return false
}
//是否信任服务端的https证书 (返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
open func webView(_ webView: WKWebView, didReceive challenge:URLAuthenticationChallenge, completionHandler: @escaping(URLSession.AuthChallengeDisposition, URLCredential?)->Void)->Bool{
return false
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68